package com.leenmvc.core.utils.dynamic;

import com.leenmvc.core.init.Constant;
import com.leenmvc.core.utils.HandlerVideo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.util.HashMap;

/**
 * 一个类加载器
 * 加载方式包括：
 * 从本地加载.class文件、java文件
 * 从网络加载.class文件、java文件
 * 加载java字符串
 * <p>
 * 使用方法：
 * 1、初始化实例
 * 2、如果需要获取class文件或java文件那么调用 setFilePath方法
 * 如果需要从网络获取class文件或java文件那么调用 setUrl方法
 * 如果需要需要加载java字符串、那么调用 setJavaStrArr方法
 */
public class MyClassLoader extends ClassLoader {

    private final static Logger logger = LoggerFactory.getLogger(MyClassLoader.class);

    public void getInstance() {
    }

    private String filePath;

    private String url;

    private String javaStr;

    private String fullName;

    private String fileName;

    private byte[] bytes;

    public String getFullName() {
        return fullName;
    }

    public MyClassLoader() {
    }

    public MyClassLoader(String filePath) {
        this.filePath = filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
        this.url = null;
        this.javaStr = null;
        setBytes();
    }

    public void setUrl(String url) {
        this.url = url;
        this.filePath = null;
        this.javaStr = null;
        setBytes();
    }

    public void setJavaStr(String javaStr) {
        this.javaStr = javaStr;
        this.filePath = null;
        this.url = null;
        setBytes();
    }

    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            return this.defineClass(name, this.bytes, 0, this.bytes.length);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return super.findClass(name);
    }

    /**
     * 获取byte数组
     *
     * @return
     * @throws Exception
     */
    private void setBytes() {
        byte[] bytes = null;
        if (filePath != null) { // 从本地获取class文件或者java文件
            File file = new File(this.filePath);
            this.bytes = getByteByFile(file);
        } else if (url != null) { // 网络上获取class文件或者java文件
            this.bytes = getByteByUrl(this.url);
        } else if (javaStr != null) { // 加载java字符串
            setFullName(this.javaStr);
            this.bytes = getJavaStrToClassBytes(this.javaStr);
        }
    }

    /**
     * 处理本地文件的加载问题
     *
     * @param file
     * @return
     * @throws Exception
     */
    private byte[] getByteByFile(File file) {
        String name = file.getAbsolutePath();
        if (name.endsWith(".java")) {
            return compile(file);
        }
        return getClassBytes(file);
    }

    /**
     * 设置全名称
     *
     * @param file
     */
    private void setFullName(File file) {
        String javaFile = readFileToString(file);
        String fullName = javaFile.substring("package".length() + 1, javaFile.indexOf(";"));
        int beginIndex = javaFile.indexOf(" class ") + 7;
        int endIndex = javaFile.indexOf("implements");
        if(endIndex == -1) {
            endIndex = javaFile.indexOf(" {");
        }
        String rName = javaFile.substring(beginIndex, endIndex).trim();
        this.fileName = rName + ".java";
        this.fullName = fullName + "." + rName;
    }

    /**
     * 设置全名称
     *
     * @param javaFile
     */
    private void setFullName(String javaFile) {
        String fullName = javaFile.substring("package".length() + 1, javaFile.indexOf(";"));
        int beginIndex = javaFile.indexOf(" class ") + 7;

        int endIndex = javaFile.indexOf("implements");
        if(endIndex == -1) {
            endIndex = javaFile.indexOf(" {");
        }

        String rName = javaFile.substring(beginIndex, endIndex).trim();
        this.fileName = rName + ".java";
        this.fullName = fullName + "." + rName;
    }

    private void setFullName(byte[] classBytes) {
        // 暂未实现
    }

    ;

    /**
     * 获取class文件的byte数组
     *
     * @param classfile
     * @return
     * @throws Exception
     */
    private byte[] getClassBytes(File classfile) {
        // 这里要读入.class的字节，因此要使用字节流
        FileInputStream fis = null;
        ByteArrayOutputStream baos = null;
        try {
            fis = new FileInputStream(classfile);
            FileChannel fc = fis.getChannel();
            baos = new ByteArrayOutputStream();
            WritableByteChannel wbc = Channels.newChannel(baos);
            ByteBuffer by = ByteBuffer.allocate(1024);
            while (true) {
                int i = fc.read(by);
                if (i == 0 || i == -1)
                    break;
                by.flip();
                wbc.write(by);
                by.clear();
            }
            fis.close();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return baos.toByteArray();
    }

    public byte[] getByteByUrl(String strUrl) {
        String fileName = strUrl.substring(strUrl.lastIndexOf("/") + 1);
        if (strUrl.endsWith("java")) {
            byte[] classBytes = getClassBytesByJavaUrl(strUrl, fileName);
            if (classBytes != null) return classBytes;
        }
        return toByteArray(getInputStreamByUrl(strUrl));
    }

    /**
     * java字符串转换成ClassByte
     *
     * @param jfileBytes
     * @return
     */
    private byte[] getJavaStrToClassBytes(String jfileBytes) {
        File file = byteArrayToFile(jfileBytes.getBytes(), this.fileName);
        return compile(file);
    }

    /**
     * 将java文件编译成class数组
     *
     * @param file java文件
     * @return
     */
    private byte[] compile(File file) {
        if (file.exists()) {
            setFullName(file);
            try {
                String absolutePath = file.getAbsolutePath();
                Process exec = Runtime.getRuntime().exec("javac -encoding UTF-8 " + absolutePath);
                new HandlerVideo.Reader(exec.getErrorStream()).start();
                exec.waitFor();
                String classPath = absolutePath.replace(".java", ".class");
                file = new File(classPath);
                return getClassBytes(file);
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
        return null;
    }

    /**
     * 网络上的java文件获取
     *
     * @param strUrl
     * @param fileName
     * @return
     */
    private byte[] getClassBytesByJavaUrl(String strUrl, String fileName) {
        byte[] jfileBytes = toByteArray(getInputStreamByUrl(strUrl));
        File file = byteArrayToFile(jfileBytes, fileName);
        byte[] file1 = compileJavaToClass(file);
        if (file1 != null) return file1;
        return null;
    }

    private byte[] compileJavaToClass(File file) {
        return compile(file);
    }

    public static InputStream getInputStreamByUrl(String strUrl) {
        HttpURLConnection conn = null;
        URL url = null;
        try {
            url = new URL(strUrl);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(20 * 1000);

            return conn.getInputStream();
        } catch (MalformedURLException e) {
            logger.error(e.getMessage(), e);
        } catch (ProtocolException e) {
            logger.error(e.getMessage(), e);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * InputStream转化为byte[]数组
     *
     * @param input
     * @return
     * @throws IOException
     */
    public static byte[] toByteArray(InputStream input) {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int n = 0;
        while (true) {
            try {
                if (!(-1 != (n = input.read(buffer)))) break;
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }
            output.write(buffer, 0, n);
        }
        return output.toByteArray();
    }


    /**
     * InputStream转化为byte[]数组
     *
     * @return
     * @throws IOException
     */
    public static File byteArrayToFile(byte[] bfile, String fileName) {
        String filePath = Constant.WORK_DIR + "/dynamic";
        File dynamicDirectory = new File(filePath);
        if (!dynamicDirectory.exists()) {
            dynamicDirectory.mkdirs();
        }
        BufferedOutputStream bos = null;
        FileOutputStream fos = null;
        File file = null;
        try {
            file = new File(filePath + "/" + fileName);
            fos = new FileOutputStream(file);
            bos = new BufferedOutputStream(fos);
            bos.write(bfile);
            return file;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
        return null;
    }

    public static String readFileToString(File file) {
        // 定义返回结果
        String jsonString = "";
        BufferedReader in = null;
        try {
            in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
            String thisLine = null;
            while ((thisLine = in.readLine()) != null) {
                jsonString += thisLine;
            }
            in.close();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException el) {
                    logger.error(el.getMessage(), el);
                }
            }
        }
        // 返回拼接好的JSON String
        return jsonString;
    }


    /**
     * 加载网络上的java文件并将该文件加载到内存调用其方法（默认方法不传参数）
     *
     * @param url
     * @param doSomething
     */
    public void loadingNetJavaAndInvoke(String url, String doSomething) {
        this.setUrl(url);
        try {
            Class<?> aClass = Class.forName(this.getFullName(), true, this);
            Object obj = aClass.newInstance();
            Method doIt = obj.getClass().getDeclaredMethod(doSomething, null);
            doIt.invoke(obj);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    /**
     * 加载网络上的java文件并将该文件加载到内存调用其方法（默认方法不传参数）
     *
     * @param path
     * @param doSomething
     */
    public void loadingLocalJavaAndInvoke(String path, String doSomething) {
        this.setFilePath(path);
        try {
            Class<?> aClass = Class.forName(this.getFullName(), true, this);
            Object obj = aClass.newInstance();
            Method doIt = obj.getClass().getDeclaredMethod(doSomething);
            doIt.invoke(obj);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    /**
     * 加载网络上的java文件并将该文件加载到内存调用其方法（默认方法不传参数）
     *
     * @param javaStr
     * @param doSomething
     */
    public HashMap<Object, Method> loadingJavaStrAndInvoke(String javaStr, String doSomething) throws Exception {
        this.setJavaStr(javaStr);
        Class<?> aClass = Class.forName(this.getFullName(), true, this);
        Object obj = aClass.newInstance();
        Method doIt = obj.getClass().getDeclaredMethod(doSomething, null);
        doIt.invoke(obj);
        return new HashMap() {
            {
                put(obj, obj.getClass().getDeclaredMethod("stop", null));
            }
        };
    }

}